#include "def.h"
#include "util.h"
#include "..\CommLib\PacketConven.h"
#include <Maya/MFn.h>
#include <Maya/MFnMesh.h>
#include <Maya/MItDependencyNodes.h>
#include <Maya/MObjectArray.h>
#include <Maya/MIntArray.h>
#include <Maya/MPlug.h>
#include <Maya/MPlugArray.h>

/**
	Determines if an object handle is a mesh

	@author Dacre Denny
	@param object the object to be tested
	@return true if object is a mesh
*/
bool VUtil::IsMesh(MObject& object) {

	return !object.isNull() && object.apiType() == MFn::kMesh;
}

/**
	Determines if an object handle is a transform

	@author Dacre Denny
	@param object the object to be tested
	@return true if object is a transform
*/
bool VUtil::IsTransform(MObject& object) {

	return !object.isNull() && object.apiType() == MFn::kTransform;
}

/**
	Gets the transformation object associated with this mesh object if the a mesh is valid
	and the transformation object exists

	@author Dacre Denny
	@param object the mesh object whose transform is being aquired
	@param transform the resulting transformation object
	@return true if successful otherwise false
*/
bool VUtil::GetMeshTransform(MObject& object, MObject& transform) {

	if(!object.hasFn(MFn::kMesh))
		return false;

	MStatus result;
	MFnDagNode dn(object, &result);
	MAYA_RETURN_IF_FAIL(result, false);

	for(uint i = 0; i < dn.parentCount(); i++) {

		MObject parentObject = dn.parent(i, &result);
		MAYA_CONTINUE_IF_FAIL(result);

		if(parentObject.hasFn(MFn::kTransform)) {
			transform = parentObject;
			return true;
		}
	}

	return false;
}

/**
	Gets the first mesh object associated with this transform object if the transform is valid

	@author Dacre Denny
	@param object the transform object whose mesh is being aquired
	@param mesh the resulting mesh object
	@return true if successful otherwise false
*/
bool VUtil::GetTransformMesh(MObject& object, MObject& mesh) {

	if(!object.hasFn(MFn::kTransform))
		return false;

	MStatus result;
	MFnDagNode dn(object, &result);
	MAYA_RETURN_IF_FAIL(result, false);

	for(uint i = 0; i < dn.childCount(); i++) {

		MObject childObject = dn.child(i, &result);
		MAYA_CONTINUE_IF_FAIL(result);

		if(childObject.hasFn(MFn::kMesh)) {
			mesh = childObject;
			return true;
		}
	}

	return false;
}

/**
	Gets the triangle count of the object if it is a mesh that is valid

	@author Dacre Denny
	@param object the object to be queried
	@param triangleCount the integer to store the triangle count
	@return true if object is a valid mesh otherwise false
*/
bool VUtil::GetMeshTriangleCount(MObject& object, u_int& triangleCount) {

	if(!VUtil::IsMesh(object)) 
		return false;
	
	MStatus result;
	MFnMesh objectMesh(object, &result);
	MAYA_RETURN_IF_FAIL(result, false);

	MIntArray objectPolygons;
	MAYA_RETURN_IF_FAIL(objectMesh.getTriangles(objectPolygons, MIntArray()), false);

	uint count = 0;
	for(u_int i = 0; i < objectPolygons.length(); i++)
		count += objectPolygons[i];

	triangleCount = count;
	return true;
}

/**
	Gets the materials of the object if it is a mesh that has materials

	@author Dacre Denny
	@param object the object to be tested
	@param materials the object array to store the materials
	@return true if object is a mesh and has materials
*/
bool VUtil::GetMeshMaterials(MObject& object, MObjectArray& materials) {

	if(VUtil::IsMesh(object)) {

		MFnMesh mesh(object);
	
		MObjectArray shaderEngines;
		MAYA_RETURN_IF_FAIL(mesh.getConnectedShaders(0, shaderEngines, MIntArray()), false);

		if(shaderEngines.length() == 0)
			return false;
		
		MStatus result;
		MFnDependencyNode shaderEngine(shaderEngines[0], &result);
		MAYA_RETURN_IF_FAIL(result, false);

		MPlug surfacePlug = shaderEngine.findPlug(MString("surfaceShader"), true, &result);
		MAYA_RETURN_IF_FAIL(result, false);

		MPlugArray plugArray;
		surfacePlug.connectedTo(plugArray,true,true,&result);
		MAYA_RETURN_IF_FAIL(result, false);

		for(u_int i = 0; i < plugArray.length(); i++) {
			
			MObject surfaceMaterial = plugArray[i].node(&result);
			MAYA_CONTINUE_IF_FAIL(result);

			if(VUtil::IsMaterialSupported(surfaceMaterial)) {
				materials.append(surfaceMaterial);
			}
		}
		return (materials.length() > 0);
	}
	return false;
}

/**
	Gets a single material of the object if it is a mesh that has materials. The material
	is specified by a zero based index

	@author Dacre Denny
	@param object the object to be tested
	@param index the material index to retrieve
	@param material the object to store the material

	@return true if object is a mesh that has the indexed material
*/
bool VUtil::GetMeshMaterial(MObject& object, u_int index, MObject& material) {

	MObjectArray materials;

	if(VUtil::GetMeshMaterials(object, materials)) {

		if(index < materials.length()) {
			material = materials[index];
			return true;
		}
	}
	return false;
}

/**
	Returns the vertex format of a mesh object. The function only succedes if
	the object is valid and a mesh, and if the required attributes exist on it

	@author Dacre Denny
	@param object the object to be analyised
	@param vertexFormat reference to the varaible that will hold the objects vertex format

	@return true if object is a mesh and consists of required attribute otherwise false
*/
bool VUtil::GetMeshVertexFormat(MObject& object, u_int& vertexFormat) {

	if(!VUtil::IsMesh(object))
		return false;
	
	MPlug attribute;
	if(!VUtil::GetObjectAttribute(object, MString("liveview_vertexformat"), attribute))
		return false;

	MString vertexFormatString;
	if(MAYA_FAIL(attribute.getValue(vertexFormatString)))
		return false;

	MStringArray vertexFormatArray;
	if(MAYA_FAIL(vertexFormatString.split(',', vertexFormatArray)))
		return false;
		
	vertexFormat = 0;
	for(uint i = 0; i < vertexFormatArray.length(); i++) {

		const MString formatElement = vertexFormatArray[i];
		if(formatElement == "P")
			vertexFormat |= VERTEX_POSITION;
		else if(formatElement == "N")
			vertexFormat |= VERTEX_NORMAL;
		else if(formatElement == "T")
			vertexFormat |= VERTEX_TANGENT;
		else if(formatElement == "UV0")
			vertexFormat |= VERTEX_UV0;
		else if(formatElement == "UV1")
			vertexFormat |= VERTEX_UV1;
		else if(formatElement == "UV2")
			vertexFormat |= VERTEX_UV2;
		else if(formatElement == "C")
			vertexFormat |= VERTEX_COLOR;
		else if(formatElement == "D")
			vertexFormat |= VERTEX_DEFORMATION;
	}

	return true;
}

/**
	Attempts to locate a procedural texture object from the specified mesh object. The result is based on the specified
	channel of which are:
	<ul>
	<li>"INSTANCE_POSITION"</li>
	<li>"INSTANCE_ROTATION"</li>
	<li>"INSTANCE_SCALE"</li>
	</ul>

	The function will only succeed if it can return a procedural texture object that is currently supported

	@author Dacre Denny
	@param targetObject the mesh object that has associated with it a procedural texture object on a channel of interest
	@param channel the channel of interest in the aquired texture object
	@param proceduralTexture the object to store the texture

	@return true if object is a mesh and it has a valid procedural texture on the specified channel otherwise false
*/
bool VUtil::GetMeshInstanceProcedrual(MObject& targetObject, MString& channel, MObject& proceduralTexture) {

#pragma message ("this function no longer needed?")
	if(!VUtil::IsMesh(targetObject))
		return false;

	MPlug channelPlug;
	if(!VUtil::GetObjectAttribute(targetObject, channel, channelPlug))
		return false;

	MString materialName;
	MAYA_RETURN_IF_FAIL(channelPlug.getValue(materialName),false);
	
	MObject materialObject;
	if(!VUtil::GetProceduralByName(materialName, materialObject))
		return false;
	
	if(!VUtil::IsMaterialSupported(materialObject))
		return false;
	
	MStatus result;
	MFnDependencyNode materialDN = MFnDependencyNode(materialObject, &result);
	MAYA_RETURN_IF_FAIL(result,false);

	MPlug colorPlug = materialDN.findPlug(MString("color"), &result);
	MAYA_RETURN_IF_FAIL(result,false);
	
	MPlugArray plugArray;
	materialDN.getConnections(plugArray);
	colorPlug.connectedTo(plugArray, true, true, &result);
	MAYA_RETURN_IF_FAIL(result,false)

	for(u_int i = 0; i < plugArray.length(); i++) {

		MFnDependencyNode plugDN = MFnDependencyNode(plugArray[i].node(), &result);
		MAYA_CONTINUE_IF_FAIL(result);

		if(VUtil::IsTextureSupported(plugDN.object())) {
			
			proceduralTexture = plugDN.object();
			return true;
		}
	}

	return false;
}

/**
	Determines if the specified object is a material and if it
	is currently supported

	@author Dacre Denny
	@param object the object to be tested

	@return true if object is a materal that is supported
*/
bool VUtil::IsMaterialSupported(MObject& object) {

	return object.hasFn(MFn::kLambert) || object.hasFn(MFn::kPhong) || object.hasFn(MFn::kBlinn);
}

/**
	Attempts to retrieve a mesh object from the current scene, whose name
	equals the name specified.

	@author Dacre Denny
	@param name the name of the object being sought
	@param targetObject the respective object if it exists

	@return true if object of the specified name is found and it is of type mesh, otherwise false
*/
bool VUtil::GetMeshByName(MString& name, MObject& targetObject) {
	
	MItDependencyNodes meshIterator(MFn::kMesh);
	for(; !meshIterator.isDone(); meshIterator.next()) {
		
		MStatus result;
		MFnDependencyNode meshDN(meshIterator.item(), &result);
		MAYA_CONTINUE_IF_FAIL(result);

		if(meshDN.name() == name && meshIterator.item().hasFn(MFn::kMesh)) {
			targetObject = meshIterator.item();			
			return true;
		}
	}

	return false;
}

/**
	Attempts to retrieve a material object from the current scene, whose name
	equals the name specified.

	@author Dacre Denny
	@param name the name of the object being sought
	@param materialObject the respective object if it exists

	@return true if object of the specified name is found and it is of a supported material type, otherwise false
*/
bool VUtil::GetMaterialByName(MString& name, MObject& materialObject) {
	
	MFn::Type materailTypes[] = {MFn::kLambert, MFn::kPhong, MFn::kBlinn};

	for(u_int i = 0; i < 3; i++) {
		
		MItDependencyNodes materialIterator(materailTypes[i]);
		for(; !materialIterator.isDone(); materialIterator.next()) {
			
			MStatus result;
			MFnDependencyNode materialDN(materialIterator.item(), &result);
			MAYA_CONTINUE_IF_FAIL(result);

			if(materialDN.name() == name) {

				if(VUtil::IsMaterialSupported(materialIterator.item())) {
					materialObject = materialIterator.item();
					return true;
				}

				return false;
			}
		}
	}
	return false;
}

/**
	Attempts to retrieve a procedrual/texture object from the current scene, whose name
	equals the name specified. First match is retrieved. Only supported procedurals will
	be returned.

	@author Dacre Denny
	@param name the name of the object being sought
	@param proceduralTextureObject the respective object if it exists

	@return true if object of the specified name is found and it is of a supported texture type, otherwise false
*/
bool  VUtil::GetProceduralByName(MString& name, MObject& proceduralTextureObject) {
	
	#pragma message ("globalize these and add global count for loops etc")
	MFn::Type proceduralTypes[] = {MFn::kChecker, MFn::kNoise, MFn::kGrid, MFn::kRamp};

	for(u_int i = 0; i < 4; i++) {
		
		MItDependencyNodes textureIterator(proceduralTypes[i]);
		for(; !textureIterator.isDone(); textureIterator.next()) {
			
			MStatus result;
			MFnDependencyNode proceduralDN(textureIterator.item(), &result);
			MAYA_CONTINUE_IF_FAIL(result);

			if(proceduralDN.name() == name) {

				if(VUtil::IsTextureSupported(textureIterator.item())) {
					proceduralTextureObject = textureIterator.item();
					return true;
				}

				return false;
			}
		}
	}
	return false;
}

/**
	Attempts to retrieve a procedrual/texture object from the current scene, whose name
	equals the name specified. First match is retrieved. Only supported procedurals will
	be returned. Texture's are also returned.

	@author Dacre Denny
	@param name the name of the object being sought
	@param proceduralTextureObject the respective object if it exists

	@return true if object of the specified name is found and it is of a supported texture type, otherwise false
*/
bool  VUtil::GetSurfaceTextureByName(MString& name, MObject& surfaceTextureObject) {
	
	#pragma message ("globalize these and add global count for loops etc")

	MFn::Type returnTypes[] = {MFn::kChecker, MFn::kNoise, MFn::kGrid, MFn::kRamp, MFn::kFileTexture};

	for(u_int i = 0; i < 5; i++) {
		
		MItDependencyNodes textureIterator(returnTypes[i]);
		for(; !textureIterator.isDone(); textureIterator.next()) {
			
			MStatus result;
			MFnDependencyNode proceduralDN(textureIterator.item(), &result);
			MAYA_CONTINUE_IF_FAIL(result);

			MString dnName = proceduralDN.name();
			if(dnName == name) {

				if(VUtil::IsTextureSupported(textureIterator.item())) {
					surfaceTextureObject = textureIterator.item();
					return true;
				}

				return false;
			}
		}
	}
	return false;
}

/**
	Determines if the specified object is a texture and if so,
	wether or not the texture is currently supported for interpretation

	@author Dacre Denny
	@param object the object to be tested

	@return true if object is a texture that is supported
*/
bool VUtil::IsTextureSupported(MObject& object) {

	return object.hasFn(MFn::kChecker) || object.hasFn(MFn::kNoise) || object.hasFn(MFn::kGrid) || object.hasFn(MFn::kRamp) || object.hasFn(MFn::kFileTexture);
}

/**
	Attempts to aquire the first node in the root's dag tree that
	is discovered to equal the function type specified.

	@author Dacre Denny
	@param root the root object that is suspected to have a child node of the specified type
	@param type the child node that is being searched for
	@param the object of the result if it is found

	@return true if a child of the specified type is found
*/
bool VUtil::TraverseDAGForType(MObject& root, MFn::Type type, MObject& node) {
	return false;
}


/**
	Attempts to a list of objects that are parents of this instance mesh. A mesh
	can be a child instance of many other mesh objects so it is therefore nessisary
	to return a list for these circumstances.

	@author Dacre Denny
	@param meshInstance the mesh object who's parents are being aquired
	@param meshInstanceParents the object array where instance parents of the child mesh are stored

	@return true if parent mesh objects are found and returned for the specified mesh instance, otherwise false
*/
bool VUtil::GetInstanceMeshParents(MObject& meshInstance, MObjectArray& meshInstanceParents) {

	if(!VUtil::IsMesh(meshInstance))
		return false;
	
	MString meshInstanceName;
	if(!VUtil::GetObjectName(meshInstance, meshInstanceName))
		return false;

	MItDependencyNodes sceneMeshIterator(MFn::kMesh);
	for(; !sceneMeshIterator.isDone(); sceneMeshIterator.next()) {
		
		MObject sceneMesh = sceneMeshIterator.item();

		MPlug instanceChildrenPlug;
		if(!VUtil::GetObjectAttribute(sceneMesh, MString("INST_CHILDREN"), instanceChildrenPlug))
			continue;

		MString childrenList;
		MAYA_CONTINUE_IF_FAIL(instanceChildrenPlug.getValue(childrenList));

		MStringArray children;
		MAYA_CONTINUE_IF_FAIL(childrenList.split(',', children));

		for(uint i = 0; i < children.length(); i++) {

			if(children[i] == meshInstanceName) {
				MAYA_RETURN_IF_FAIL(meshInstanceParents.append(sceneMesh), false);
				break;
			}
		}
	}

	return true;
}

/**
	Attempts to aquire the first node in the root's connection tree that
	is discovered to equal the function type specified.

	@author Dacre Denny
	@param root the root object that is suspected to have a child node of the specified type
	@param attributeName the name of the attribute to aquire as an object
	@param attributePlug the plug of the result if it is found

	@return true if the attribute on the specified root is found
*/
bool VUtil::GetObjectAttribute(MObject& root, MString& attributeName, MPlug& attributePlug) {
	
	if(attributeName.length() == 0)
		return false;

	MStatus result;
	MFnDependencyNode rootDag = MFnDependencyNode(root, &result);
	MAYA_RETURN_IF_FAIL(result, false)
		
	attributePlug = rootDag.findPlug(attributeName, true, &result);
	MAYA_RETURN_IF_FAIL(result, false)
	return true;
}

/**
	Attempts to aquire the name of the specified object.

	@author Dacre Denny
	@param object the object who's name is being aquired
	@param objectName the name of the specified object

	@return true if the name is successfully aquired
*/
bool VUtil::GetObjectName(MObject& object, MString& objectName) {

	MStatus result;
	MFnDependencyNode objectDN = MFnDependencyNode(object, &result);
	MAYA_RETURN_IF_FAIL(result, false)
	
	objectName = objectDN.name();
	return true;
}

/**
	Attempts to aquire the first decal texture object in the material. If
	material is not a material this function will fail.
	
	@author Dacre Denny
	@param material the material object that is being searched for the texture
	@param textureObject the object of the result if it is found

	@return true if the texture on the specified material is found
*/
bool VUtil::GetMaterialDecal(MObject& material, MObject& textureObject) {

	if(!VUtil::IsMaterialSupported(material))
		return false;

	MStatus result;
	MFnDependencyNode materialDN = MFnDependencyNode(material, &result);
	MAYA_RETURN_IF_FAIL(result, false)

	MPlug targetPlug = materialDN.findPlug(MString("color"), &result);
	MAYA_RETURN_IF_FAIL(result, false)
		
	MPlugArray plugArray;
	targetPlug.connectedTo(plugArray, true, true, &result);
	MAYA_RETURN_IF_FAIL(result, false)

	for(u_int i = 0; i < plugArray.length(); i++) {

		MFnDependencyNode plugDN = MFnDependencyNode(plugArray[i].node(), &result);
		MAYA_CONTINUE_IF_FAIL(result)

		if(plugDN.object().hasFn(MFn::kFileTexture)) {
	
			MPlug filenamePlug = plugDN.findPlug(MString("fileTextureName"), &result);
			MAYA_CONTINUE_IF_FAIL(result)

			textureObject = filenamePlug.node();
			return true;
		}
	}

	return false;
}

/**
	Attempts to aquire the first decal normal object in the material. If
	material is not a material this function will fail.
	
	@author Dacre Denny
	@param material the material object that is being searched for the texture
	@param textureObject the object of the result if it is found

	@return true if the texture on the specified material is found
*/
bool VUtil::GetMaterialNormal(MObject& material, MObject& textureObject) {

	if(!VUtil::IsMaterialSupported(material))
		return false;

	MStatus result;
	MFnDependencyNode materialDN = MFnDependencyNode(material, &result);
	MAYA_RETURN_IF_FAIL(result, false)

	MPlug targetPlug = materialDN.findPlug(MString("normalCamera"), &result);
	MAYA_RETURN_IF_FAIL(result, false)
		
	MPlugArray plugArray;
	targetPlug.connectedTo(plugArray, true, true, &result);
	MAYA_RETURN_IF_FAIL(result, false)

	for(u_int i = 0; i < plugArray.length(); i++) {

		MFnDependencyNode plugDN = MFnDependencyNode(plugArray[i].node(), &result);
		MAYA_CONTINUE_IF_FAIL(result)

		if(plugDN.object().hasFn(MFn::kBump)) {
	
			MFnDependencyNode bumpDN = MFnDependencyNode(plugDN.object(), &result);
			MAYA_CONTINUE_IF_FAIL(result)
	
			MPlug plugBump = bumpDN.findPlug(MString("bumpValue"), &result);
			MAYA_CONTINUE_IF_FAIL(result)
	
			MPlugArray plugBumpArray;
			plugBump.connectedTo(plugBumpArray, true, true, &result);
			MAYA_CONTINUE_IF_FAIL(result)
	
			for(u_int j = 0; j < plugBumpArray.length(); j++) {

				MFnDependencyNode textureDN = MFnDependencyNode(plugBumpArray[j].node(), &result);
				MAYA_CONTINUE_IF_FAIL(result)
				
				if(textureDN.object().hasFn(MFn::kFileTexture)) {
					textureObject = textureDN.object();
					return true;
				}
			}
		}
	}

	return false;
}

/**
	Attempts to aquire the first specular texture object in the material. If
	material is not a material this function will fail.
	
	@author Dacre Denny
	@param material the material object that is being searched for the texture
	@param textureObject the object of the result if it is found

	@return true if the texture on the specified material is found
*/
bool VUtil::GetMaterialSpecular(MObject& material, MObject& textureObject) {

	if(!VUtil::IsMaterialSupported(material))
		return false;

	MStatus result;
	MFnDependencyNode materialDN = MFnDependencyNode(material, &result);
	MAYA_RETURN_IF_FAIL(result, false)

	MPlug targetPlug = materialDN.findPlug(MString("specularColor"), &result);
	MAYA_RETURN_IF_FAIL(result, false)
		
	MPlugArray plugArray;
	targetPlug.connectedTo(plugArray, true, true, &result);
	MAYA_RETURN_IF_FAIL(result, false)

	for(u_int i = 0; i < plugArray.length(); i++) {

		MFnDependencyNode plugDN = MFnDependencyNode(plugArray[i].node(), &result);
		MAYA_CONTINUE_IF_FAIL(result)

		if(plugDN.object().hasFn(MFn::kFileTexture)) {
	
			MPlug filenamePlug = plugDN.findPlug(MString("fileTextureName"), &result);
			MAYA_CONTINUE_IF_FAIL(result)

			textureObject = filenamePlug.node();
			return true;
		}
	}

	return false;
}

/**
	Attempts to aquire the texture filename for an object that has a texture filename
	function set.
	
	@author Dacre Denny
	@param texture the texture object that is being searched for the texture filename
	@param filename the object that stores the filename if it is found

	@return true if the filename of the specified texture object is found
*/
bool VUtil::GetTextureFilename(MObject& texture, MString& filename) {

	if(!texture.hasFn(MFn::kFileTexture))
		return false;

	/*
	CONSIDER THIS:


	   case MFn::kFileTexture: {

        textureFilenamePlug = texture.findPlug(MString("fileTextureName"), &status);
        if(MAYA_SUCCESS(status)) {
                if(MAYA_SUCCESS(textureFilenamePlug.getValue(textureFilename))) {
                        *pFilename = textureFilename;
                        break;
                }
        
	*/

	MStatus result;
	MFnDependencyNode textureFilenameDN = MFnDependencyNode(texture, &result);
	MAYA_RETURN_IF_FAIL(result, false);

	MPlug textureFilenamePlug = textureFilenameDN.findPlug("fileTextureName", &result);
	MAYA_RETURN_IF_FAIL(result, false);

	return MAYA_SUCCESS(textureFilenamePlug.getValue(filename));
}